package com.xuyaxu.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Random;

/**
 * 秒杀案例控制层
 * 使用redis事务解决秒杀超卖问题，但是会造成库存遗留问题
 *
 * @author xuyaxu
 * @date 2022/8/20 15:46
 */
@RestController
@RequestMapping("secKill")
public class SecKillController {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @GetMapping("")
    public void secKillDemo() {
        // 生成用户id（大小在 0-50000之间，包含0不包含50000）
        String userId = new Random().nextInt(50000) + "";
        String productId = "001";
        this.secKillService(userId, productId);
    }

    /**
     * 秒杀的具体实现代码
     *
     * @param userId    用户id
     * @param productId 商品id
     */
    @SuppressWarnings("all")
    private void secKillService(String userId, String productId) {
        // 拼接库存key
        String kcKey = "sk:" + productId + ":qt";
        // 秒杀成功用户key
        String userKey = "sk:" + productId + ":user";

        // 业务处理耗时
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 获取库存，如果库存为null，则秒杀还没有开始
        Integer kc = (Integer) redisTemplate.opsForValue().get(kcKey);
        if (kc == null) {
            System.out.println("秒杀还没有开始，请等待");
            return;
        }

        // 判断是否已参与过秒杀
        if (redisTemplate.opsForSet().isMember(userKey, userId)) {
            System.out.println("已经秒杀成功了，不能重复秒杀！");
            return;
        }
        // 获取目前库存数量
        Integer kcNum = (Integer) redisTemplate.opsForValue().get(kcKey);

        // 判断如果商品库存数量小于1，秒杀结束
        if (kcNum <= 0) {
            System.out.println("秒杀已经结束了！");
            return;
        }

        Object execute = redisTemplate.execute(new SessionCallback() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                // 解决超卖问题：监视库存（因为库存在发生变化）当该key被其它客户端改变时,则会中断当前的操作
                operations.watch(kcKey);
                // 解决超卖问题：开启事务
                operations.multi();

                // 一下是组队操作（加库存，添加到清单中）
                // 进行秒杀-减库存
                operations.opsForValue().decrement(kcKey);
                // 把秒杀成功的用户添加到清单里面
                operations.opsForSet().add(userKey, userId);

                // 解决超卖问题：提交事务
                return operations.exec();
            }
        });

        if (CollectionUtils.isEmpty((List<Object>) execute)) {
            System.out.println(execute);
            System.out.println("秒杀失败了...");
            return;
        }
        System.out.println(userId + "秒杀成功了！");
    }
}
